import pandas as pd
import numpy as np
from sklearn.decomposition import PCA  # 主成分分析库，用于对数据进行主成分分析降维操作
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA  # 线性判别分析库，可基于类别信息进行降维
import matplotlib.pyplot as plt  # 绘图库，用于可视化降维后的数据

# 加载数据集，并返回指定列名的数据
def load_data(url, columns):
    """
    此函数用于从给定的URL地址读取CSV格式的数据集，并按照指定的列名进行命名。

    参数:
    url (str): 数据集的URL地址，指向一个CSV文件。
    columns (list): 一个字符串列表，用于给读取的数据集中的列命名。

    返回值:
    data (pd.DataFrame 或 None): 如果读取成功，返回一个带有指定列名的DataFrame类型的数据集；若读取过程中出现错误，则返回None。
    """
    try:
        # 使用pandas读取CSV文件，header=None表示没有标题行，names参数用来命名列
        data = pd.read_csv(url, header=None, names=columns)
    except Exception as e:
        # 捕获异常并打印错误信息，这里可以更具体地处理不同类型的异常，比如文件不存在、格式错误等，目前只是简单打印
        print(f"加载数据时出错: {e}")
        return None
    return data

# 绘制降维后的数据散点图
def plot_reduction(X_reduced, y, title, labels=('第一主成分', '第二主成分')):
    """
    此函数用于绘制降维后的数据散点图，根据降维后的维度情况来确定坐标设置，并添加相应的图表元素，如标题、坐标轴标签、颜色条等。

    参数:
    X_reduced (numpy.ndarray): 降维后的数据，形状为 (样本数量, 降维后的维度)。
    y (numpy.ndarray 或 pd.Series): 对应的类别标签，用于给散点图上色，以区分不同类别。
    title (str): 图表的标题，描述该图所展示的降维方法及相关信息。
    labels (tuple): 一个包含两个字符串的元组，分别用于设置x轴和y轴的标签，默认是('第一主成分', '第二主成分')，对于一维情况可按需修改。
    """
    plt.figure(figsize=(8, 6))
    # 根据降维后的数据绘制散点图，如果是一维则y轴全部设为0
    # 这里通过判断降维后数据的维度来确定y轴坐标的设置，若维度大于1则正常取第二维数据作为y轴坐标，否则全部设为0
    if X_reduced.shape[1] > 1:
        scatter = plt.scatter(X_reduced[:, 0], X_reduced[:, 1],
                              c=y, cmap='viridis', edgecolor='k', s=50)
    else:
        scatter = plt.scatter(X_reduced[:, 0], [0] * len(X_reduced),
                              c=y, cmap='viridis', edgecolor='k', s=50)
    plt.title(title)  # 设置图表标题
    plt.xlabel(labels[0])  # 设置x轴标签
    plt.ylabel(labels[1])  # 设置y轴标签
    plt.colorbar(scatter, label='类别标签')  # 添加颜色条并设置标签，用于展示不同颜色对应的类别
    plt.grid()  # 显示网格线，方便查看数据分布
    plt.show()

# 加载红酒数据集
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data"
columns = ['Class', 'Alcohol', 'Malic_acid', 'Ash', 'Alcalinity_of_ash', 'Magnesium',
           'Total_phenols', 'Flavanoids', 'Nonflavanoid_phenols', 'Proanthocyanins',
           'Color_intensity', 'Hue', 'OD280/OD315', 'Proline']
data = load_data(url, columns)

if data is not None:
    # 筛选类别为1和2的数据，目的是简化分析场景，只关注这两类数据的降维情况
    filtered_data = data[data['Class'].isin([1, 2])]
    # 特征数据（去除类别列），获取除了类别列之外的所有列作为特征用于后续的降维操作
    X = filtered_data.iloc[:, 1:]
    # 类别标签，单独提取出类别列作为后续绘图区分不同类别的依据
    y = filtered_data['Class']

    # PCA - 将特征降维到二维
    pca = PCA(n_components=2)
    # 使用PCA模型对特征数据进行拟合和转换，将原始高维特征数据降维到二维
    X_pca = pca.fit_transform(X)
    print("PCA将特征降维到两个维度：")
    print(X_pca)

    # 绘制PCA结果
    plot_reduction(X_pca, y, 'PCA: 特征降维到二维')

    # LDA - 将特征降维到一维（对于两类问题，LDA降维后只能为一维）
    lda = LDA(n_components=1)
    # 使用LDA模型对特征数据和对应的类别标签进行拟合，并将特征数据降维到一维，这里传入类别标签y是因为LDA会利用类别信息进行降维
    X_lda = lda.fit_transform(X, y)
    print("LDA将特征降维到一个维度：")
    print(X_lda)

    # 绘制LDA结果
    plot_reduction(X_lda, y, 'LDA: 特征降维到一维', labels=('线性判别成分', ''))